﻿using System.Drawing;
using System.Drawing.Drawing2D;
using Library.Util;
using System;

namespace Library.Winform.Draw {
	/// <summary>
	/// Line graphic object
	/// </summary>
	public class LineDraw : ObjectDraw {
		public const ushort FromHandle = 1;
		public const ushort ToHandle = 2;
		protected static readonly ushort[] Handles = new[] { FromHandle, ToHandle };
		private Point _from;
		private Point _to;
		/// <summary>
		/// Get or set start point
		/// </summary>
		public Point From {
			get { return _from; }
			set {
				bool change = _from.X != value.X || _from.Y != value.Y;
				if (change) {
					_from.X = value.X;
					_from.Y = value.Y;

					DisposeHitGraphic();
				}
			}
		}

		/// <summary>
		/// Get or set end point
		/// </summary>
		public Point To {
			get { return _to; }
			set {
				var change = _to.X != value.X || _to.Y != value.Y;
				if (change) {
					_to.X = value.X;
					_to.Y = value.Y;
					DisposeHitGraphic();
				}
			}
		}

		/// <summary>
		///  Graphic objects for hit test
		/// </summary>

		protected GraphicsPath AreaPath;
		protected Pen AreaPen;
		protected Region AreaRegion;
		/// <summary>
		/// For serialization from stream
		/// </summary>
		public LineDraw()
			: this(0, 0, 1, 0) {

		}
		public LineDraw(int fromX, int fromY, int toX, int toY) {
			_from.X = fromX;
			_from.Y = fromY;
			_to.X = toX;
			_to.Y = toY;
		}
		public override void ReadFrom(System.IO.Stream stream) {
			int args;
			stream.Read(out args);
			_from.X = args;
			stream.Read(out args);
			_from.Y = args;
			stream.Read(out args);
			_to.X = args;
			stream.Read(out args);
			_to.Y = args;
			base.ReadFrom(stream);
		}

		public override void WriteTo(System.IO.Stream stream) {
			stream.Write(_from.X);
			stream.Write(_from.Y);
			stream.Write(_to.X);
			stream.Write(_to.Y);
			base.WriteTo(stream);
		}


		public override ushort[] HandlePoints {
			get { return Handles; }
		}

		public override Point MaxPosition {
			get {
				var x = _from.X > _to.X ? _from.X : _to.X;
				var y = _from.Y > _to.Y ? _from.Y : _to.Y;
				return new Point(x, y);
			}
		}

		public override Point MinPosition {
			get {
				var x = _from.X < _to.X ? _from.X : _to.X;
				var y = _from.Y < _to.Y ? _from.Y : _to.Y;
				return new Point(x, y);
			}
		}

		protected override Point GetLocation() {
			return _from;
		}

		protected override void DrawMe(Graphics g) {
			g.SmoothingMode = SmoothingMode.AntiAlias;
			var linePen = new Pen(PenColor, PenWidth);
			Pen shadowPen = ShadowLen > 0 ? new Pen(ShadowColor, ShadowLen) : null;
			try {
				DrawLine(g, linePen, shadowPen, ShadowLen, From.X - ScrollOffset.X, From.Y - ScrollOffset.Y, To.X - ScrollOffset.X,
								 To.Y - ScrollOffset.Y);
			} finally {
				try {
					linePen.Dispose();
					// ReSharper disable EmptyGeneralCatchClause
				} catch {
					// ReSharper restore EmptyGeneralCatchClause
				}

				if (shadowPen != null) {
					try {
						shadowPen.Dispose();
						// ReSharper disable EmptyGeneralCatchClause
					} catch {
						// ReSharper restore EmptyGeneralCatchClause
					}
				}
			}
		}

		/// <summary>
		/// Draw line
		/// </summary>
		/// <param name="g"></param>
		/// <param name="linePen"></param>
		/// <param name="shadowPen">null: do not draw shadow</param>
		/// <param name="shadowOffset">the offset for shadow line (ShadowLen)</param>
		/// <param name="fromX">X Start position with scrolloffset</param>
		/// <param name="fromY">Y Start position with scrolloffset</param>
		/// <param name="toX">X End position with scrolloffset</param>
		/// <param name="toY">Y End position with scrolloffset</param>
		protected static void DrawLine(Graphics g, Pen linePen, Pen shadowPen, int shadowOffset, int fromX, int fromY, int toX, int toY) {
			g.DrawLine(linePen, fromX, fromY, toX, toY);
			if (shadowPen != null && shadowOffset > 0) {
				g.DrawLine(shadowPen, fromX + shadowOffset, fromY + shadowOffset, toX + shadowOffset, toY + shadowOffset);
			}
		}

		public override void Normalize() {
			if (_from.X > _to.X) {
				var tem = _to;
				_to = _from;
				_from = tem;
			}
		}

		protected override void MoveTo(int offsetX, int offsetY) {
			_from.X += offsetX;
			_from.Y += offsetY;

			_to.X += offsetX;
			_to.Y += offsetY;
			DisposeHitGraphic();
		}

		protected override void MoveHandleTo(ushort pointHandler, Point newPoint) {
			switch (pointHandler) {
				case FromHandle:
					From = newPoint;
					break;
				case ToHandle:
					To = newPoint;
					break;
			}
		}

		public override Point GetPointFromHandle(ushort pointHandler) {
			switch (pointHandler) {
				case FromHandle:
					return From;
				case ToHandle:
					return To;
			}

			return base.GetPointFromHandle(pointHandler);
		}

		public override bool ContainPoint(Point point) {
			InitHitGrapphic();
			return AreaRegion.IsVisible(point);
		}

		protected virtual void InitHitGrapphic() {
			if (AreaPath != null)
				return;

			// Create path which contains wide line
			// for easy mouse selection
			AreaPath = new GraphicsPath();
			AreaPen = new Pen(Color.Black, 7);
			AreaPath.AddLine(From, To);
			AreaPath.Widen(AreaPen);

			// Create region from the path
			AreaRegion = new Region(AreaPath);
		}

		public override bool IntersectsWith(Rectangle rectangle) {
			InitHitGrapphic();

			return AreaRegion.IsVisible(rectangle);
		}

		public override object Clone() {
			var clone = new LineDraw();
			FillOriginalTo(clone);
			return clone;
		}

		protected void FillOriginalTo(LineDraw lineDraw) {
			lineDraw._to = _to;
			lineDraw._from = _from;
			base.FillOriginalTo(lineDraw);
		}

		protected override void DisposeHitGraphic() {
			if (_from.X == _to.X && _from.Y == _to.Y) {
				_to.X++;
				_to.Y++;
			}
			if (AreaPath != null) {
				AreaPath.Dispose();
				AreaPath = null;
			}

			if (AreaPen != null) {
				AreaPen.Dispose();
				AreaPen = null;
			}

			if (AreaRegion != null) {
				AreaRegion.Dispose();
				AreaRegion = null;
			}
		}


		public PointF Center {
			get { return new PointF((From.X + To.X) / 2F, (From.Y + To.Y) / 2F); }
		}

		public static bool LimitIntersectLines(int xA1, int yA1, int xA2, int yA2, int xB1, int yB1, int xB2, int yB2, out PointF intersection) {
			if (IntersectLines(xA1, yA1, xA2, yA2, xB1, yB1, xB2, yB2, out  intersection)) {
				int minaX, maxaX, minaY, maxaY;
				if (xA1 < xA2) {
					minaX = xA1;
					maxaX = xA2;
				} else {
					minaX = xA2;
					maxaX = xA1;
				}

				if (yA1 < yA2) {
					minaY = yA1;
					maxaY = yA2;
				} else {
					minaY = yA2;
					maxaY = yA1;
				}


				int minbX, maxbX, minbY, maxbY;
				if (xB1 < xB2) {
					minbX = xB1;
					maxbX = xB2;
				} else {
					minbX = xB2;
					maxbX = xB1;
				}

				if (yB1 < yB2) {
					minbY = yB1;
					maxbY = yB2;
				} else {
					minbY = yB2;
					maxbY = yB1;
				}
				return minaX <= intersection.X &&
							 intersection.X <= maxaX &&
							 minbX <= intersection.X &&
							 intersection.X <= maxbX &&
							 minaY <= intersection.Y &&
							 intersection.Y <= maxaY &&
							 minbY <= intersection.Y &&
							 intersection.Y <= maxbY;
			}

			return false;
		}
		public static bool LimitIntersectLines(Point pointA1, Point pointA2, Point pointB1, Point pointB2, out PointF intersection) {
			return LimitIntersectLines(pointA1.X, pointA1.Y, pointA2.X, pointA2.Y, pointB1.X, pointB1.Y, pointB2.X, pointB2.Y,
														out intersection);
		}
		
		public static bool IntersectLines(int xA1, int yA1, int xA2, int yA2, int xB1, int yB1, int xB2, int yB2, out PointF intersection) {
			intersection = Point.Empty;
			// normal line pointA-pointB to standard: aX + by = e
			// normal line pointC-pointD to standard: cX + dy = f
			var a = yA1 - yA2;
			var b = xA2 - xA1;
			var e = xA1 * (yA1 - yA2) - yA1 * (xA1 - xA2);

			var c = yB1 - yB2;
			var d = xB2 - xB1;
			var f = xB1 * (yB1 - yB2) - yB1 * (xB1 - xB2);


			var det = a * d - b * c;
			if (det == 0) return false;
			var x = (e * d - b * f) / det;
			var y = (a * f - c * e) / det;
			intersection = new PointF(x, y);
			return true;
		}

		public static bool IntersectLines(Point pointA1, Point pointA2, Point pointB1, Point pointB2, out PointF intersection) {
			return IntersectLines(pointA1.X, pointA1.Y, pointA2.X, pointA2.Y, pointB1.X, pointB1.Y, pointB2.X, pointB2.Y,
														out intersection);
		}

		public static bool IntersectLines(Point pointA, Point pointB, out PointF intersection) {
			return IntersectLines(Point.Empty, pointA, Point.Empty, pointB, out intersection);
		}

		public static bool LineContaintPoint(int lineFromX, int lineFromY, int lineToX, int lineToY, int pointX, int pointY) {
			if (lineFromX == lineToX) {
				return lineFromX == pointX;
			}

			if (lineFromY == lineToY) {
				return lineFromY == pointY;
			}
			return (pointY - lineFromY) * (lineToX - lineFromX) == (pointX - lineFromX) * (lineToY - lineFromY);
		}
		public static bool LimitLineContaintPoint(int lineFromX, int lineFromY, int lineToX, int lineToY, int pointX, int pointY) {
			if (IsOuter(lineFromX, lineFromY, lineToX, lineToY, pointX, pointY)) return false;
			return LineContaintPoint(lineFromX, lineFromY, lineToX, lineToY, pointX, pointY);
		}

		public static bool LimitLineContaintPoint(Point from, Point to, Point point) {
			return LimitLineContaintPoint(from.X, from.Y, to.X, to.Y, point.X, point.Y);
		}

		private static bool IsOuter(int lineFromX, int lineFromY, int lineToX, int lineToY, int pointX, int pointY) {
			int from;
			int to;
			if (lineFromX < lineToX) {
				from = lineFromX;
				to = lineToX;
			} else {
				from = lineToX;
				to = lineFromX;
			}

			if (!(from <= pointX && pointX <= to)) return true;

			if (lineFromY < lineToY) {
				from = lineFromY;
				to = lineToY;
			} else {
				from = lineToY;
				to = lineFromY;
			}
			if (!(from <= pointY && pointY <= to)) return true;

			return false;
		}
	}
}
